package org.example.readwritelock;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;
import org.example.readwritelock.entity.Emp;
import sun.util.resources.cldr.zh.CalendarData_zh_Hans_HK;

import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁 --- 缓存应用
 *
 * @author ZhengDp
 * @Date 2023/1/19 20:10
 */
@Slf4j(topic = "c.TestGenericDao")
public class TestGenericDao {

    public static void main(String[] args) {
        GenericDao dao = new GenericDaoCached();
        log.info("=========== > 查询");
        String sql = "select * from emp where empno = ?";
        int empno = 7369;
        Emp emp = dao.queryOne(Emp.class, sql, empno);
        log.info("{}", emp);
        emp = dao.queryOne(Emp.class, sql, empno);
        log.info("{}", emp);
        emp = dao.queryOne(Emp.class, sql, empno);
        log.info("{}", emp);

        log.info("========== > 更新");
        dao.update("update emp set sal = ? where empno = ?", 1000, empno);
        emp = dao.queryOne(Emp.class, sql, empno);
        log.info("{}", emp);
    }

}

class GenericDaoCached extends GenericDao {
    private GenericDao dao = new GenericDao();

    // HashMap 线程不安全，多线程访问下，会有线程安全问题
    private Map<SqlPair, Object> cache = new HashMap<>();

    private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();

    /**
     * 多个线程执行该操作 ---> 都走到 cache.get() --> 缓存中没有
     * ---> 查询数据库 ---> 数据还未设置进去 ---> 还是要执行多次的数据库查询操作
     */
    @Override
    public <T> T queryOne(Class<T> clazz, String sql, Object... args) {
        // 从缓存中找，找到直接返回
        rw.readLock().lock();
        SqlPair key = new SqlPair(sql, args);
        ;
        T value;
        try {
            value = (T) cache.get(key);
            if (Objects.nonNull(value)) {
                return value;
            }
        } finally {
            rw.readLock().unlock();
        }
        rw.writeLock().lock();
        try{
            // 多个线程获取写锁 ---> 双重检查
            value = (T) cache.get(key);
            if(Objects.isNull(value)){
                // 缓存中没有，查询数据库
                value = dao.queryOne(clazz, sql, args);
                cache.put(key, value);
            }
            return value;
        }finally {
            rw.writeLock().unlock();
        }

    }

    @Override
    public <T> List<T> queryList(Class<T> clazz, String sql, Object... args) {
        return super.queryList(clazz, sql, args);
    }

    /**
     * 清空缓存 和 更新数据库 是两个操作 --> 这两个操作 可能造成数据的不同步（数据不一致）
     */
    @Override
    public void update(String sql, Object... args) {
        rw.writeLock().lock();
        try {
            dao.update(sql, args);
            // 清空缓存
            cache.clear();
        } finally {
            rw.writeLock().unlock();
        }
    }

    class SqlPair {
        private String sql;
        private Object[] args;

        public SqlPair(String sql, Object[] args) {
            this.sql = sql;
            this.args = args;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            SqlPair sqlPair = (SqlPair) o;
            return Objects.equals(sql, sqlPair.sql) && Arrays.equals(args, sqlPair.args);
        }

        @Override
        public int hashCode() {
            int result = Objects.hash(sql);
            result = 31 * result + Arrays.hashCode(args);
            return result;
        }
    }
}

